using System;
using GameEngine.Navigation;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace GameEngine.Units
{
	public class MoveTo : Action
	{
		private static Random RandomBlocker = new Random();

		private MoveTo(Unit owner)
			: base(owner)
		{
			this.Steps = new Queue<WayPoint>();
			this.PathBlock = 0;
		}

		public MoveTo(Unit owner, WayPoint targetPoint, float distance)
			: this(owner)
		{
			this.Distance = MathHelper.Max(0.01f, distance);
			this.TargetPoint = targetPoint;
		}

		public MoveTo(Unit owner, Unit targetUnit, float distance)
			: this(owner)
		{
			this.Distance = MathHelper.Max(targetUnit.Radius + owner.Radius + 5, distance);
			this.TargetUnit = targetUnit;
		}

		public float Distance { get; private set; }
		public WayPoint TargetPoint { get; private set; }
		public Unit TargetUnit { get; private set; }

		protected Queue<WayPoint> Steps { get; private set; }
		protected int PathBlock { get; private set; }

		protected internal override bool Update(ref GameTime gameTime)
		{
			// TODO: If next step sux recalculate the path
			if (this.PathBlock > 0)
			{
				this.PathBlock--;
				if (this.PathBlock == 0)
				{
					this.Steps.Clear();
				}
			}
			if (this.Steps.Count > 0)
			{
				if (!this.Owner.CanMoveTo(Steps.Peek().Position))
				{
					this.Steps.Clear(); // pathing information is old. someone blocked in the way
				}
			}
			if (this.Steps.Count == 0 && this.PathBlock == 0)
			{
				// TODO: if blocked - wait for a while
				// TODO: set the FindPath method to be a method on the World so the NavigationMap is actually no longer required as well as the obstacles...
				if (this.TargetUnit != null)
				{
					this.TargetPoint = this.TargetUnit.WayPoint;
				}
				if (!this.World.IsBusy)
				{
					IList<WayPoint> path = this.World.FindPath(this.Owner, this.TargetPoint, this.Distance); // NavigationMap.FindPath(this.Owner.WayPoint, this.TargetPoint, this.Distance, this.Owner.Radius, null);
					if (path.Count == 0)
					{
						this.PathBlock = MoveTo.RandomBlocker.Next(45, 90);
					}
					// TODO: ah crap. if we are about to use tokens with access to pathfinding resources we'd better do not discard any pathing information computed here
					int count = path.Count;
					if (this.TargetUnit != null)
					{
						bool areClose = this.Owner.WayPoint.Parent == this.TargetPoint.Parent || this.TargetPoint.Parent.Neighbours.Contains(this.Owner.WayPoint.Parent);
						if (count > 3 && count < 16 && areClose)
						{
							count = 2; // guess the other unit will not be more than 2 times faster we could keep 1/3 of the steps and update after that
						}
					}
					if (count > 8)
					{
						count = 8; // no need to keep more steps the rest of the path was probably calculated without path blockers anyway...
					}
					for (int i = count == 1 ? 0 : 1; i < count; i++)
					{
						this.Steps.Enqueue(path[i]);
					}
				}
			}
			if (this.Steps.Count > 0)
			{
				this.Owner.PlayAnimation("Run");
				float travelDistance = this.Owner.MovementSpeed * (gameTime.ElapsedGameTime.Ticks / 10000000f);
				while (this.Steps.Count > 0 && travelDistance > 0)
				{
					// TODO: before moving check if actually the new place is not blocked - expose a Move method in the World
					WayPoint nextStep = this.Steps.Peek();
					float nextStepDistance = Vector3.Distance(this.Owner.Position, nextStep.Position);
					if (travelDistance >= nextStepDistance)
					{
						travelDistance -= nextStepDistance;
						this.Owner.Position = nextStep.Position;
						this.Steps.Dequeue();
					}
					else
					{
						this.Owner.Position = Vector3.Lerp(this.Owner.Position, nextStep.Position, travelDistance / nextStepDistance);
						travelDistance = 0;
					}
					this.Owner.UpdateWayPoint();
				}
				// Rotate to look at the next target point
				if (this.Steps.Count > 0)
				{
					WayPoint nextStep = Steps.Peek();
					Vector3 direction = nextStep.Position - this.Owner.Position;
					if (direction.Length() > 0.1)
					{
						float oldRotation = MathHelper.WrapAngle(this.Owner.Rotation);
						float targetRotation = MathHelper.WrapAngle((float)Math.Atan2(direction.Y, direction.X));
						if (MathHelper.Distance(oldRotation, targetRotation) > MathHelper.Pi)
						{
							if (oldRotation < targetRotation)
							{
								targetRotation -= MathHelper.TwoPi;
							}
							else
							{
								targetRotation += MathHelper.TwoPi;
							}
						}
						this.Owner.Rotation = MathHelper.WrapAngle(MathHelper.Lerp(oldRotation, targetRotation, 0.2f));
					}
				}
			}
			else
			{
				this.Owner.PlayAnimation("Idle");
			}
			return Vector3.Distance(this.Owner.Position, this.TargetPoint.Position) < this.Distance;
		}
	}
}
